Skip to content

gh-154199: Fix find_msvcrt() on builds with a truncated sys.version#154209

Open
SynaptSea wants to merge 1 commit into
python:mainfrom
SynaptSea:fix-find-msvcrt-clang
Open

gh-154199: Fix find_msvcrt() on builds with a truncated sys.version#154209
SynaptSea wants to merge 1 commit into
python:mainfrom
SynaptSea:fix-find-msvcrt-clang

Conversation

@SynaptSea

Copy link
Copy Markdown

Fixes #154199

Python/getversion.c formats the compiler identification with "%.80s". The banner
that PC/pyconfig.h emits for clang-cl builds is
"[Clang " __clang_version__ "] 64 bit (AMD64) with MSC v.<n> CRT]", and official LLVM
Windows binaries have an 86-character __clang_version__ (version + repo URL + full
commit hash), so the tail carrying the "MSC v." marker is always cut off.

ctypes.util._get_build_version() looks for that marker and, not finding it, fell back
to return 6 — "assume the compiler is MSVC 6". find_msvcrt() then maps version 6 to
msvcrt.dll and find_library('c') returns it. That CRT does not share its errno with
the ucrt that _ctypes is linked against, so ctypes.get_errno() silently returns stale
values after a failing call. No exception is raised — the caller just gets wrong data.

This reports the build version as unknown instead, so find_msvcrt() and
find_library('c') return None exactly as they already do on modern MSVC builds
(where _get_build_version() returns 14.x and the "CRT is no longer directly loadable"
branch from bpo-23606 applies). find_msvcrt() already had a version is None guard, so
this simply routes truncated banners into the existing safe path.

While fixing that I found a second manifestation of the same truncation: when the cut
lands inside the version digits, the marker is present but the number is not, and
s, rest = sys.version[i:].split(" ", 1) raises
ValueError: not enough values to unpack out of find_library('c') — worse than a wrong
return value. partition() handles both cases. Happy to split that into its own PR if
you would rather keep this one to the single change.

The documented contract is unchanged: Doc/library/ctypes.rst already says find_msvcrt()
returns None "if the name of the library cannot be determined", and that find_library("c")
fails on Windows. Only the undocumented MSVC-6 fallback is removed.

Testing

Added FindLibraryWindows to Lib/test/test_ctypes/test_find.py. It patches sys.version
with unittest.mock, so it exercises the fix on any Windows build — a clang-cl build is not
needed to run it. It covers a truncated clang banner, a truncation inside the marker, and a
real MSVC banner (asserting 14.4 / None, so the MSVC path is pinned against regression).

On a local clang-cl free-threaded build, python -m test test_ctypes:

  • before: run=674 failures=1 (test_errno.Test.test_open: AssertionError: 0 != 2) — FAILURE
  • after: run=678 skipped=40 — SUCCESS

test_open now skips with 'Unable to find C library', identical to an MSVC build. Three
tests move from run to skipped (test_errno.test_open, test_loading.test_find,
test_callbacks.test_issue_8959_a); all three already skip on MSVC builds, so this converges
clang-cl onto the reference platform rather than reducing coverage. Verified no MSVC
regression by mocking real banners: MSC v.1944 -> 14.4, MSC v.1600 -> msvcr100.dll,
MSC v.1200 -> msvcrt.dll (a genuine MSVC 6 build is unaffected).

The root cause is arguably the "%.80s" precision in Python/getversion.csys.version
also ends mid-hash without its closing bracket on these builds, and it broke
sysconfig.get_platform() the same way in gh-145410. I have not touched it here since that
would change sys.version content for every consumer, but it may be worth a separate issue.

…sion

Python/getversion.c formats the compiler identification with \\%.80s\\, so the
long banner emitted by clang-cl builds is cut off before the \\MSC v.\\ marker
that _get_build_version() looks for. It then fell back to \\
eturn 6\\, i.e.
'assume MSVC 6', and find_msvcrt() handed out msvcrt.dll -- a CRT that does not
share its errno with the ucrt that _ctypes is linked against, so
ctypes.get_errno() silently returned stale values.

Report the build version as unknown instead, so find_msvcrt() and
find_library('c') return None exactly as they do on modern MSVC builds.

Also handle a second truncation case found while fixing this one: when the cut
lands inside the version number itself the marker is present but the digits are
not, and the unpacking assignment raised ValueError out of find_library('c').
Copilot AI review requested due to automatic review settings July 20, 2026 01:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ctypes.util.find_msvcrt() returns "msvcrt.dll" on clang-cl Windows builds, breaking ctypes.get_errno()

2 participants